/** 
    
    This file is part of Critter.
    
    Copyright (c) 2009 Robin Southern, http://www.nxogre.org
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
    
*/

#include "CritterStable.h"
#include "CritterAnimatedCharacter.h"
#include "CritterRenderSystem.h"
#include "CritterAnimatedCharacterDescription.h"

                                                                                       

namespace Critter
{

static const NxOgre::Real ANIMATED_CHARACTER_MIN_AIR_TIME = 0.2f;

                                                                                       

AnimatedCharacter::AnimatedCharacter(const NxOgre::Vec3& globalPos, const NxOgre::Radian& yaw, const NxOgre::SimpleShape& shape, const AnimatedCharacterDescription& description, RenderSystem* rendersystem)
: NxOgre::CharacterController(rendersystem->getScene()),
  mRenderSystem(rendersystem),
  mSceneManager(rendersystem->getSceneManager()),
  mSceneNodeDestructorBehaviour(description.mSceneNodeDestructorBehaviour),
  mRenderPriority(description.mRenderPriority),
  mYaw(yaw),
  mUpAxis(0,1,0),
  mTurningYaw(0),
  mMaxTurningYaw(description.mMaxTurningYaw),
  mDirection(1,0,0,0),
  mWalkingVelocity(description.mWalkingVelocity),
  mRunningVelocity(description.mRunningVelocity),
  mJumpVelocity(description.mJumpVelocity),
  mHorizontalAirModifier(description.mHorizontalAirModifier),
  mGravityModifier(description.mGravityModifier),
  mAirTimer(0),
  mInAir(false),
  mMoveVector(0,0,0),
  mMomentum(0,0,0),
  mNodeOffset(description.mNodeOffset)
{
 
 // Set the name and hash.
 mName = description.mName;
 mNameHash = NxOgre::Strings::hash(mName);

 mSceneManager = rendersystem->getSceneManager();
 mNode = description.mNode;
 
 mRunningWalkingDiff = mRunningVelocity - mWalkingVelocity;
 mDimensions = shape.to_cc_shape();
 mIsCapsule = mDimensions.z != 0.0f;
 
 createCharacterController(globalPos, shape, description);
 
 mScene->addSimulateListener(this, NxOgre::Enums::Priority_MediumHigh);
 mScene->addRenderListener(this, mRenderPriority);
}

AnimatedCharacter::~AnimatedCharacter()
{
 mScene->removeRenderListener(this, mRenderPriority);
 mScene->removeSimulateListener(this, NxOgre::Enums::Priority_MediumHigh);

 _destructNode(mSceneNodeDestructorBehaviour);
 
 // Destructor of CharacterController will delete the CharacterController for us.
}

NxOgre::Real AnimatedCharacter::getHorizontalVelocity(NxOgre::Real stride) const
{
 
 if (stride == 0)
  return 0;
 
 NxOgre::Real ret = mWalkingVelocity + (NxOgre::Math::abs(stride) * mRunningWalkingDiff);
 
 if (stride < 0)
  ret = -ret;
 return ret;
 
}

NxOgre::Real AnimatedCharacter::getRunningVelocity() const
{
 return mRunningVelocity;
}

void AnimatedCharacter::setRunningVelocity(NxOgre::Real vel)
{
 mRunningVelocity = vel;
 mRunningWalkingDiff = mRunningVelocity - mWalkingVelocity;
}

NxOgre::Real AnimatedCharacter::getWalkingVelocity() const
{
 return mWalkingVelocity;
}

void AnimatedCharacter::setWalkingVelocity(NxOgre::Real vel)
{
 mWalkingVelocity = vel;
 mRunningWalkingDiff = mRunningVelocity - mWalkingVelocity;
}

NxOgre::Real AnimatedCharacter::getGravityModifier() const
{
 return mGravityModifier;
}

void AnimatedCharacter::setGravityModifier(NxOgre::Real gravMod)
{
 mGravityModifier = gravMod;
}

NxOgre::Real AnimatedCharacter::getJumpVelocity() const
{
 return mJumpVelocity;
}

void AnimatedCharacter::setJumpVelocity(NxOgre::Real vel)
{
 mJumpVelocity = vel;
}

unsigned int AnimatedCharacter::getRigidBodyType() const
{
 return Critter::Enums::RigidBodyType_AnimatedCharacter;
}

Ogre::SceneManager* AnimatedCharacter::getSceneManager()
{
 return mSceneManager;
}

Ogre::SceneNode* AnimatedCharacter::getSceneNode()
{
 return mNode;
}

void AnimatedCharacter::setSceneNode(Ogre::SceneNode* node, Enums::SceneNodeDestructorBehaviour behaviour)
{
 _destructNode(behaviour);
 mNode = node;
}

Enums::SceneNodeDestructorBehaviour AnimatedCharacter::getSceneNodeDestructorBehaviour() const
{
 return mSceneNodeDestructorBehaviour;
}

void AnimatedCharacter::setSceneNodeDestructorBehaviour(Enums::SceneNodeDestructorBehaviour behaviour)
{
 mSceneNodeDestructorBehaviour = behaviour;
}

void AnimatedCharacter::_destructNode(Enums::SceneNodeDestructorBehaviour behaviour)
{
 
 if (mNode == 0)
  return;
 
 if (behaviour == Enums::SceneNodeDestructorBehaviour_Inherit)
  behaviour = mSceneNodeDestructorBehaviour;
 
 if (behaviour == Enums::SceneNodeDestructorBehaviour_Destroy)
 {
  // Remove all attachments.
  if (mNode->numAttachedObjects())
   mNode->detachAllObjects();
  
  // Destroy all child scenenodes.
  if (mNode->numChildren())
   mNode->removeAndDestroyAllChildren();
  
  // Destroy this Scene node.
  mNode->getParentSceneNode()->removeAndDestroyChild(mNode->getName());
  mNode = 0;
 }
 else
 {
  mNode->getParentSceneNode()->removeChild(mNode);
  mNode;
 }
 
}

bool AnimatedCharacter::advance(float deltaTime, const NxOgre::Enums::Priority&, const NxOgre::Enums::SceneFunction& func)
{
 
 if (func == NxOgre::Enums::SceneFunction_Simulate)
  _processSituation(deltaTime);
 else 
 if (func == NxOgre::Enums::SceneFunction_Render)
  _processRendering(deltaTime);
 
 return true;
}

NxOgre::Vec3  AnimatedCharacter::getHeadPosition() const
{
 NxOgre::Vec3 ret = getPosition();
 if (mIsCapsule)
  ret.y += (mDimensions.y * 0.5f) + (mDimensions.x*2);
 else
  ret.y += (mDimensions.y * 0.5f);
 return ret;
}

NxOgre::Vec3  AnimatedCharacter::getFootPosition() const
{
 NxOgre::Vec3 ret = getPosition();
 if (mIsCapsule)
  ret.y -= (mDimensions.y * 0.5f) + (mDimensions.x*2);
 else
  ret.y -= (mDimensions.y * 0.5f);
 ret.y -= (getStepOffset() + getSkinWidth());
 return ret;
}

NxOgre::Vec3 AnimatedCharacter::getRenderPosition() const
{
 return getPosition() + mNodeOffset;
}

NxOgre::Vec3 AnimatedCharacter::getMoveVector() const
{
 return mMoveVector;
}

void AnimatedCharacter::clearMoveVector()
{
 mMoveVector.zero();
}

void AnimatedCharacter::turn(const Ogre::Radian& rad)
{
 turn(NxOgre::Radian(rad.valueRadians()));
}

void AnimatedCharacter::move(const Ogre::Vector3& vec)
{
 AnimatedCharacter::move(NxOgre::Vec3(vec));
}

void AnimatedCharacter::turn(const NxOgre::Radian& rad)
{
 mTurningYaw += rad;
 if (mTurningYaw > mMaxTurningYaw)
  mTurningYaw = mMaxTurningYaw;
 
}

void AnimatedCharacter::move(const NxOgre::Vec3& normalised_movement_vector)
{
 mMoveVector += normalised_movement_vector;
 mMoveVector.clamp(NxOgre::Vec3::NEGATIVE_1_1_1, NxOgre::Vec3::POSITIVE_1_1_1);
}

void AnimatedCharacter::_processSituation(float deltaTime)
{
 // CALCULATE_HORIZONTAL_MOVEMENT
 //////////////////////////////////////////////////////////
 mMoveVector.x = getHorizontalVelocity(mMoveVector.x);
 mMoveVector.z = getHorizontalVelocity(mMoveVector.z);
 
 if (mInAir)
 {
  mMoveVector.x *= mHorizontalAirModifier;
  mMoveVector.z *= mHorizontalAirModifier;
 }
 
 //////////////////////////////////////////////////////////

 // CALCULATE_VERTICAL_MOVEMENT
 //////////////////////////////////////////////////////////
 if (mInAir == false)
 {
  if (mMoveVector.y > 0)
   mMoveVector.y *= mJumpVelocity;
 }
 //////////////////////////////////////////////////////////

 // CALCULATE_GRAVITY_ACCELERATION
 //////////////////////////////////////////////////////////
 if (mGravityModifier != 0.0f && lastCollisionDown() == false && mInAir)
  mMoveVector += (mScene->getGravity() * mGravityModifier);
 //////////////////////////////////////////////////////////

 
 // CALCULATE_TURNING
 //////////////////////////////////////////////////////////
 mYaw += mTurningYaw;
 // TODO: Multiply vector with yaw for a direction.
 mDirection.fromAngleAxis(mYaw, mUpAxis);
 //////////////////////////////////////////////////////////
 
 // PLANE CHECK
 //////////////////////////////////////////////////////////
 if (getFootPosition().y < 0)
 {
  mMoveVector.y = -getFootPosition().y;
 }
 
 // MULTIPLY_VECTOR_BY_TIME_PASSED
 //////////////////////////////////////////////////////////
 mMoveVector *= deltaTime;
 //////////////////////////////////////////////////////////
 
 // APPLY CHANGES
 //////////////////////////////////////////////////////////
 
 CharacterController::move(mMoveVector);
 mMoveVector.zero();
 mTurningYaw = 0;
 //////////////////////////////////////////////////////////


 // POST COLLISION CHECK
 //////////////////////////////////////////////////////////
 // TODO: Check flags to see if off ground or touched something.
 //
 // Check to see if we are in the air.
 if (lastCollisionDown() == false)
 {
  
  mAirTimer += deltaTime;
  
  // Check to see if we've been in the air long enough to stop any real movement.
  if (mAirTimer >= ANIMATED_CHARACTER_MIN_AIR_TIME)
  {
   mInAir = true;
   // TODO: Store a copy of the velocity at this point and add it to future updates whilst in
   // air to keep the momentum.
   // animate falling begin
   // call callback
  }
  else
  {
   // animated falling loop
  }
  
 }
 else if (lastCollisionDown())
 {
  // We've been in the air for more than MIN_AIR_TIME
  if (mInAir)
  {
   mInAir = false;
   // animated falling end
   // call callback.
  }
  
  // Reset the air timer if needed.
  if (mAirTimer)
   mAirTimer = 0.0f;
  
 }
 //
 //
 //////////////////////////////////////////////////////////
 
}

void AnimatedCharacter::_processRendering(float deltaTime)
{
 mNode->setPosition(getRenderPosition().as<Ogre::Vector3>());
 mNode->setOrientation(mDirection.as<Ogre::Quaternion>());
}

                                                                                       

}

                                                                                       

